﻿using System;
using System.Collections;
using System.IO;


namespace FileTreeSpace
{
    /// <summary>
    /// DirRoot is a DirItem plus watchers and an inorder iterator.
    /// </summary>
    public class DirRoot : DirItem, IEnumerable
    {
        private FileSystemWatcher watcher = null;
        public event FileCreateEventHandler FileCreateSubs;
        public event FileChangeEventHandler FileChangeSubs;
        public event FileRenameEventHandler FileRenameSubs;
        public event FileDeleteEventHandler FileDeleteSubs;
        public event DirCreateEventHandler DirCreateSubs;
        public event DirRenameEventHandler DirRenameSubs;
        public event DirDeleteEventHandler DirDeleteSubs;


        public DirRoot (string newName)
            : base (newName)
        {
            string s = Path.GetFullPath (newName);

            // For drive roots.
            if (s[s.Length - 1] == '\\')
                s = s.Substring (0, s.Length - 1);

            if (!Directory.Exists (s))
                throw new ArgumentException ("Directory not found");

            init (s);
            loadTree ();
        }


        /// <summary>Add the file system watchers.</summary>
        public void Watch ()
        {
            watcher = new FileSystemWatcher (Name);
            watcher.Created += new FileSystemEventHandler (OnCreate);
            watcher.Changed += new FileSystemEventHandler (OnChange);
            watcher.Renamed += new RenamedEventHandler (OnRename);
            watcher.Deleted += new FileSystemEventHandler (OnDelete);
            watcher.NotifyFilter |= NotifyFilters.Attributes;
            watcher.IncludeSubdirectories = true;
            watcher.EnableRaisingEvents = true;
        }


        // A created directory may already have subdirectories so notify and recurse.
        private void traverseDirs (string fullPathName, DirItem dir)
        {
            DirCreateSubs (fullPathName, dir.Name);

            for (int i = 0; i < dir.Dirs.Count; ++i)
                traverseDirs (fullPathName + @"\" + dir.Dirs[i].Name, dir.Dirs[i]);
        }


        /// <summary>
        /// A file or directory has been created.
        /// Duplicate this event in FileTree and notify appropriate subscribers to the event.
        /// </summary>
        /// <param name="sender">FileSystemWatcher that initiated this event.</param>
        /// <param name="evArgs">File system details of creation.</param>
        private void OnCreate (object sender, FileSystemEventArgs e)
        {
            FileInfo info = new FileInfo (e.FullPath);
            if ((info.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
            {
                DirPath parentPath = new DirPath (this, e.FullPath.Substring (0, e.FullPath.Length - info.Name.Length - 1));
                DirItem newNode = parentPath.NodeRef (info.Name);
                DirectoryInfo di = new DirectoryInfo (e.FullPath);

                if (di.Exists)
                    // Recurse into all subdirectories and build the tree.
                    newNode.buildDown (di);

                if (DirCreateSubs != null)
                    // Recurse into all FileTree nodes and notify.
                    traverseDirs (e.FullPath, newNode);
            }
            else
            {
                DirPath path = new DirPath (this, e.FullPath);
                path.FileRef (info.Name, info.Length, info.LastWriteTime, info.Attributes);

                if (FileCreateSubs != null)
                    FileCreateSubs (e.FullPath, info.Name, info.Length, info.LastWriteTime, info.Attributes);
            }
        }


        /// <summary>
        /// A file or directory has had its attributes or contents changed.
        /// Duplicate this event in FileTree and notify appropriate subscribers to the event.
        /// </summary>
        /// <param name="sender">FileSystemWatcher that initiated this event.</param>
        /// <param name="evArgs">File system details of change.</param>
        private void OnChange (object sender, FileSystemEventArgs e)
        {
            lock (this)
            {
                DirPath p = new DirPath (this, e.FullPath);

                if (p.IsFile)
                {
                    FileItem file = p.File;
                    FileInfo info = new FileInfo (e.FullPath);
                    file.Hash = null;
                    file.Size = info.Length;
                    file.Attributes = info.Attributes;

                    if (FileChangeSubs != null)
                        FileChangeSubs (e.FullPath, file.Size, file.Attributes);
                }
                else
                {
                    // nothing to do for directory change
                }
            }
        }


        /// <summary>
        /// A file or directory has been renamed.
        /// Duplicate this event in FileTree and notify appropriate subscribers to the event.
        /// </summary>
        /// <param name="sender">FileSystemWatcher that initiated this event.</param>
        /// <param name="evArgs">File system details of rename.</param>
        private void OnRename (object sender, RenamedEventArgs evArgs)
        {
            int i1 = evArgs.OldFullPath.LastIndexOf ('\\') + 1;
            string oldRightmostName = evArgs.OldFullPath.Substring (i1);
            int i2 = evArgs.FullPath.LastIndexOf ('\\') + 1;
            string newRightmostName = evArgs.FullPath.Substring (i2);

            lock (this)
            {
                DirPath p = new DirPath (this, evArgs.OldFullPath);
                if (p.IsFile)
                {
                    // Perform file rename and notify.
                    p.File.Name = newRightmostName;
                    if (FileRenameSubs != null)
                        FileRenameSubs (evArgs.OldFullPath, evArgs.FullPath, newRightmostName);
                }
                else
                {
                    // Perform directory rename and notify.
                    p.Leaf.Name = newRightmostName;
                    if (DirRenameSubs != null)
                        DirRenameSubs (evArgs.OldFullPath, oldRightmostName, newRightmostName);
                }
            }
        }


        /// <summary>
        /// A file or directory has been deleted.
        /// Duplicate this event in FileTree and notify appropriate subscribers to the event.
        /// </summary>
        /// <param name="sender">FileSystemWatcher that initiated this event.</param>
        /// <param name="evArgs">File system details of delete.</param>
        private void OnDelete (object sender, FileSystemEventArgs evArgs)
        {
            bool isFile;
            lock (this)
            {
                DirPath p = new DirPath (this, evArgs.FullPath);
                isFile = p.IsFile;

                if (isFile)
                    p.FileUnreference ();
                else
                    p.NodeUnreference ();
            }
            if (isFile)
            {
                if (FileDeleteSubs != null)
                    FileDeleteSubs (evArgs.FullPath);
            }
            else
                if (DirDeleteSubs != null)
                    DirDeleteSubs (evArgs.FullPath);
        }


        public IEnumerator GetEnumerator ()
        {
            return new DirEnumerator (this);
        }


        /// <summary>Perform inorder traversal of a DirRoot.</summary>
        public class DirEnumerator : DirPath, IEnumerator
        {
            public DirEnumerator (DirRoot startDir) : base (startDir) { }

            object IEnumerator.Current { get { return (DirPath) this; } }
            public void Reset () { Init (); }

            public Boolean MoveNext ()
            {
                return Advance ();
            }
        }

    }
}
